package code

import "fmt"

func ExportFile(file *File) (*File, error) {
	structs := make([]*Struct, len(file.Structs))
	for i, s := range file.Structs {
		ns, err := exportStruct(s, file.PackageName)
		if err != nil {
			return nil, err
		}
		structs[i] = ns
	}
	interfaces := make([]*InterfaceSpec, len(file.Interfaces))
	// todo implement export interface
	for i, iterf := range file.Interfaces {
		ni, err := exportInterface(iterf, file.PackageName)
		if err != nil {
			return nil, err
		}
		interfaces[i] = ni
	}
	f := File{
		PackageName: file.PackageName,
		Imports:     file.Imports,
		Structs:     structs,
		Interfaces:  interfaces,
	}
	return &f, nil
}

func exportStruct(s *Struct, packageAlias string) (*Struct, error) {
	fields := make([]StructField, len(s.Fields))
	for i, f := range s.Fields {
		extType, err := toExternalType(f.Type, packageAlias)
		if err != nil {
			return nil, err
		}
		fields[i] = StructField{
			Name: f.Name,
			Type: extType,
			Tags: f.Tags,
		}
	}
	str := Struct{
		PackageAlias: packageAlias,
		Name:         s.Name,
		Fields:       fields,
	}
	return &str, nil
}

func exportInterface(spec *InterfaceSpec, packageAlias string) (*InterfaceSpec, error) {
	methods := make([]*Function, len(spec.Methods))
	for i, m := range spec.Methods {
		params := make([]Param, len(m.Params))
		for j, pa := range m.Params {
			paramType, err := toExternalType(pa.Type, packageAlias)
			if err != nil {
				return nil, err
			}
			param := Param{Name: pa.Name, Type: paramType}
			params[j] = param
		}
		returns := make([]Type, len(m.Returns))
		for j, re := range m.Returns {
			retType, err := toExternalType(re, packageAlias)
			if err != nil {
				return nil, err
			}
			returns[j] = retType
		}
		methods[i] = &Function{
			Name:     m.Name,
			Params:   params,
			Returns:  returns,
			Comments: m.Comments,
		}
	}
	return &InterfaceSpec{Name: spec.Name, Methods: methods}, nil
}

func toExternalType(p Type, packageAlias string) (Type, error) {
	switch t := p.(type) {
	case ExternalType:
		return ExternalType{Name: t.Name, PackageAlias: t.PackageAlias}, nil
	case SimpleType:
		if t.IsBuildIn() {
			return t, nil
		}
		return ExternalType{Name: t.Code(), PackageAlias: packageAlias}, nil
	case ArrayType:
		containedType, err := toExternalType(t.ContainedType, packageAlias)
		if err != nil {
			return nil, err
		}
		return ArrayType{ContainedType: containedType}, nil
	case PointerType:
		containedType, err := toExternalType(t.ContainedType, packageAlias)
		if err != nil {
			return nil, err
		}
		return PointerType{ContainedType: containedType}, nil
	case MapType:
		valueType, err := toExternalType(t.ValueType, packageAlias)
		if err != nil {
			return nil, err
		}
		keyType, err := toExternalType(t.KeyType, packageAlias)
		if err != nil {
			return nil, err
		}
		return MapType{keyType, valueType}, nil
	default:
		return nil, fmt.Errorf("unSupported type to convert to External type")
	}
}
